技术背景
当前,我们基于导航菜单的显示和操作按钮的禁用状态,实现了页面可见性和操作可用性的权限验证,或者叫访问控制。但这仅限于页面的显示和操作,我们的后台接口还是没有进行权限的验证,只要知道了后台的接口信息,就可以直接通过swagger或自行发送ajax请求成功调用后台接口,这是非常危险的。接下来,我们就基于Shiro的注解式权限控制方案,来给我们的后台接口提供权限保护。
权限注解
Shiro总共有5个权限注解,实现了不同的权限控制策略。
RequiresPermissions
当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。
这是基于资源权限方式的权限控制主要方案,也是我们项目中进行权限控制使用的注解方案。
RequiresRoles
当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
RequiresUser
当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。
RequiresAuthentication
使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。
RequiresGuest
使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。
注解优先级
Shiro的认证注解处理具有内定处理顺序,如有多个注解,会按照下面优先级逐个检查,只有所有检查通过才允许访问:
RequiresRoles
RequiresPermissions
RequiresAuthentication
RequiresUser
RequiresGuest
代码实现
添加配置
打开kitty-admin工程,找到shiro配置类。添加如下内容,主要作用是开启Shiro的权限注解。
Shiro通过AOP方式拦截被权限注解的类或方法,然后匹配权限注解值和用户权限列表进行验证。
ShiroConfig.java
/**
* Shiro生命周期处理器
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
添加注解
以菜单管理接口为例,添加 @RequiresPermissions("权限标识") 标识即可。
这个权限标识就是我们的菜单表中对应的权限标识字段(perms)对应的值。
SysMenuController.java
package com.louis.kitty.admin.controller;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.louis.kitty.admin.model.SysMenu;
import com.louis.kitty.admin.sevice.SysMenuService;
import com.louis.kitty.core.http.HttpResult;
/**
* 菜单控制器
* @author Louis
* @date Oct 29, 2018
*/
@RestController
@RequestMapping("menu")
public class SysMenuController {
@Autowired
private SysMenuService sysMenuService;
@RequiresPermissions({"sys:menu:add", "sys:menu:edit"})
@PostMapping(value="/save")
public HttpResult save(@RequestBody SysMenu record) {
return HttpResult.ok(sysMenuService.save(record));
}
@RequiresPermissions("sys:menu:delete")
@PostMapping(value="/delete")
public HttpResult delete(@RequestBody List records) {
return HttpResult.ok(sysMenuService.delete(records));
}
@RequiresPermissions("sys:menu:view")
@GetMapping(value="/findNavTree")
public HttpResult findNavTree(@RequestParam String userName) {
return HttpResult.ok(sysMenuService.findTree(userName, 1));
}
@RequiresPermissions("sys:menu:view")
@GetMapping(value="/findMenuTree")
public HttpResult findMenuTree() {
return HttpResult.ok(sysMenuService.findTree(null, 0));
}
}
测试效果
启动服务,通过Swagger分别使用超级管理员和测试人员角色账户访问接口,发现admin可以正常访问,无权限的账户访问返回如下权限验证失败信息。
{
"timestamp": "2018-11-19T07:58:21.532+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Subject does not have permission [sys:menu:view]",
"path": "/menu/findMenuTree"
}
原理剖析
首先在Shiro配置的时候,我们配置了一个 AuthorizationAttributeSourceAdvisor 类。
/**
* Shiro生命周期处理器
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
在 AuthorizationAttributeSourceAdvisor 类中,我们看到了有关五个权限注解的信息,以及关联一个拦截器 AopAllianceAnnotationsAuthorizingMethodInterceptor。
public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {private static final Class |